package qt

/*
#include <stdlib.h>
#include "qtbinding/qtbinding.h"
*/
// #cgo LDFLAGS: -L./build -lqtbinding -Wl,-rpath=\$ORIGIN/
import "C"

import (
    _ "embed"
    "fmt"
    "errors"
    "unsafe"
    "reflect"
    "unicode"
    "strings"
    "kumachan/standalone/qt/cgohelper"
)


// QObject
type Object struct {
    ptr  unsafe.Pointer
}
func (obj Object) findChild(name string) (Object, bool) {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var ptr = C.QtObjectFindChild(obj.ptr, new_str(name))
    if ptr != nil {
        return Object{ptr}, true
    } else {
        return Object{}, false
    }
}
func (obj Object) FindChild(name string, v reflect.Value) error {
    var child, ok = obj.findChild(name)
    if !(ok) {
        return errors.New(fmt.Sprintf("child Object %s not found", name))
    }
    var err = child.ExactlyAssignTo(v)
    if err != nil {
        return fmt.Errorf("child %s: %w", name, err)
    }
    return nil
}
func (obj Object) ExactlyAssignTo(v reflect.Value) error {
    if v.Kind() != reflect.Ptr { panic("invalid argument") }
    var class = obj.GetClassName()
    var required = (func() string {
        var type_name = v.Elem().Type().Name()
        if strings.HasPrefix(type_name, "Custom") {
            return strings.TrimPrefix(type_name, "Custom")
        } else {
            return ("Q" + type_name)
        }
    })()
    if class == required {
        var target = v.Elem().Field(0)
        for !(reflect.TypeOf(obj).AssignableTo(target.Type())) {
            target = target.Field(0)
        }
        target.Set(reflect.ValueOf((interface{})(obj)))
        return nil
    } else {
        return errors.New(fmt.Sprintf(
            "class not matching: %s (%s)", class, required))
    }
}
func (obj Object) ExactlyAssignChildrenTo(v reflect.Value) error {
    if v.Kind() != reflect.Ptr { panic("invalid argument") }
    if v.Elem().Kind() != reflect.Struct { panic("invalid argument") }
    var struct_v = v.Elem()
    var struct_t = struct_v.Type()
    for i := 0; i < struct_t.NumField(); i += 1 {
        var field_name = struct_t.Field(i).Name
        var field_type = struct_t.Field(i).Type
        var t = ([] rune)(field_type.Name())
        t[0] = unicode.ToLower(t[0])
        var field_type_name = string(t)
        var child_name = (field_type_name + field_name)
        var err = obj.FindChild(child_name, struct_v.Field(i).Addr())
        if err != nil { return err }
    }
    return nil
}
func (obj Object) BlockSignals() (error, func()) {
    C.QtBlockSignals(obj.ptr, 1)
    return nil, func() {
        // NOTE: previous blocking state NOT recovered (always unblock)
        C.QtBlockSignals(obj.ptr, 0)
    }
}
func (obj Object) BlockCallbacks() (error, func()) {
    C.QtBlockCallbacks(obj.ptr, 1)
    return nil, func() {
        // NOTE: previous blocking state NOT recovered (always unblock)
        C.QtBlockCallbacks(obj.ptr, 0)
    }
}
func (obj Object) GetClassName() string {
    return ConsumeString(String(C.QtObjectGetClassName(obj.ptr)))
}
func (obj Object) getPropQtString(prop string) String {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var value = C.QtObjectGetPropString(obj.ptr, new_str(prop))
    return String(value)
}
func (obj Object) setPropQtString(prop string, val String) {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    C.QtObjectSetPropString(obj.ptr, new_str(prop), C.QtString(val))
}
func (obj Object) GetPropString(prop string) string {
    var value = obj.getPropQtString(prop)
    var value_runes = ConsumeString(value)
    return value_runes
}
func (obj Object) SetPropString(prop string, val string) {
    var q_val, del_str = NewString(val)
    defer del_str()
    obj.setPropQtString(prop, q_val)
}
func (obj Object) GetPropBool(prop string) bool {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var val = C.QtObjectGetPropBool(obj.ptr, new_str(prop))
    return getBool(val)
}
func (obj Object) SetPropBool(prop string, val bool) {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var int_val int
    if val {
        int_val = 1
    } else {
        int_val = 0
    }
    C.QtObjectSetPropBool(obj.ptr, new_str(prop), C.int(int_val))
}
func (obj Object) GetPropInt(prop string) int {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    return int(C.QtObjectGetPropInt(obj.ptr, new_str(prop)))
}
func (obj Object) SetPropInt(prop string, val int) {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    C.QtObjectSetPropInt(obj.ptr, new_str(prop), C.int(val))
}

// QWidget
type Widget struct { Object }
func (w Widget) Show() {
    C.QtWidgetShow(w.ptr)
}
func (w Widget) MoveToScreenCenter() {
    C.QtWidgetMoveToScreenCenter(w.ptr)
}
func (w Widget) FindChildAssumeWidget(name string) (Widget, bool) {
    var child, ok = w.findChild(name)
    if ok {
        return Widget{child}, true
    } else {
        return Widget{}, false
    }
}
func (w Widget) FindChildAssumeAction(name string) (Action, bool) {
    var child, ok = w.findChild(name)
    if ok {
        return Action{child}, true
    } else {
        return Action{}, false
    }
}
func (w Widget) SetEnabled(value bool) {
    w.SetPropBool("enabled", value)
}
func (w Widget) OnFocusOut(cb func()) func() {
    return Listen(w.Object, EventFocusOut(), false, func(_ Event) {
        cb()
    })
}

// Subclasses
type _ struct {}
type Action struct { Object }
type MainWindow struct { Widget }
type Dialog struct { Widget }
type WebEngineView struct { Widget }
type Label struct { Widget }
type PushButton struct { Widget }
type LineEdit struct { Widget }
type PlainTextEdit struct { Widget }
type ListWidget struct { Widget }
type TreeWidget struct { Widget }
type TextBrowser struct { Widget }
type GroupBox struct { Widget }
type WebWindow struct { MainWindow }
type CustomWebView struct { WebEngineView }

// Events
type _ struct {}
type Event C.QtEvent
type EventKind uint
func EventMove() EventKind { return EventKind(uint(C.QtEventMove)) }
func EventResize() EventKind { return EventKind(uint(C.QtEventResize)) }
func EventClose() EventKind { return EventKind(uint(C.QtEventClose)) }
func EventFocusIn() EventKind { return EventKind(uint(C.QtEventFocusIn)) }
func EventFocusOut() EventKind { return EventKind(uint(C.QtEventFocusOut)) }
func EventDynamicPropertyChange() EventKind { return EventKind(uint(C.QtEventDynamicPropertyChange)) }
func (ev Event) ResizeEventGetWidth() uint {
    return uint(C.QtResizeEventGetWidth(C.QtEvent(ev)))
}
func (ev Event) ResizeEventGetHeight() uint {
    return uint(C.QtResizeEventGetHeight(C.QtEvent(ev)))
}
func (ev Event) DynamicPropertyChangeEventGetPropertyName() string {
    return ConsumeString(String(C.QtDynamicPropertyChangeEventGetPropertyName(C.QtEvent(ev))))
}

// Utility Types
type _ struct {}
type String C.QtString
type ByteArray C.QtByteArray
type VariantMap C.QtVariantMap
type Icon C.QtIcon
type Pixmap C.QtPixmap
type Point struct {
    X  int
    Y  int
}
type Color struct {
    H  uint
    S  uint
    L  uint
    A  uint
}
func getBool(number C.QtBool) bool {
    return (number != 0)
}
func makeQtBool(p bool) C.QtBool {
    if p {
        return C.int(int(1))
    } else {
        return C.int(int(0))
    }
}
func getPoint(p C.QtPoint) Point {
    return Point { X: int(C.QtPointGetX(p)), Y: int(C.QtPointGetY(p)) }
}
func makeQtPoint(p Point) C.QtPoint {
    return C.QtMakePoint(C.int(p.X), C.int(p.Y))
}

func NewString(data string) (String, func()) {
    var str C.QtString
    if len(data) > 0 {
        var hdr = (*reflect.StringHeader)(unsafe.Pointer(&data))
        var ptr = (*C.uint8_t)(unsafe.Pointer(hdr.Data))
        var size = (C.size_t)(len(data))
        str = C.QtNewStringUTF8(ptr, size)
    } else {
        str = C.QtNewStringUTF8(nil, 0)
    }
    return String(str), func() {
        C.QtDeleteString(str)
    }
}
func NewStringFromUtf8Binary(data ([] byte)) (String, func()) {
    var str C.QtString
    if len(data) > 0 {
        var ptr, size = addrlen(data)
        str = C.QtNewStringUTF8(ptr, size)
    } else {
        str = C.QtNewStringUTF8(nil, 0)
    }
    return String(str), func() {
        C.QtDeleteString(str)
    }
}
func copyString(str String) string {
    var q_str = (C.QtString)(str)
    var size16 = uint(C.QtStringUTF16Length(q_str))
    if size16 == 0 {
        return ""
    }
    var buf = make([] rune, size16)
    if size16 > 0 {
        var size32 = uint(C.QtStringWriteToUTF32Buffer(q_str,
            (*C.uint32_t)(unsafe.Pointer(&buf[0]))))
        buf = buf[:size32]
    }
    return string(buf)
}
func ConsumeString(str String) string {
    var go_str = copyString(str)
    C.QtDeleteString((C.QtString)(str))
    return go_str
}

func ConsumeByteArray(b ByteArray) ([] byte) {
    var buf = unsafe.Pointer(C.QtByteArrayGetBuffer(C.QtByteArray(b)))
    var size = int(C.QtByteArrayGetSize(C.QtByteArray(b)))
    var data = make([] byte, size, size)
    for i := 0; i < size; i += 1 {
        data[i] = *(*byte)(unsafe.Pointer(uintptr(buf) + uintptr(i)))
    }
    C.QtDeleteByteArray(C.QtByteArray(b))
    return data
}

func VariantMapGetString(m VariantMap, key String) string {
    var val = C.QtVariantMapGetString(C.QtVariantMap(m), C.QtString(key))
    var val_runes = ConsumeString(String(val))
    return val_runes
}
func VariantMapGetFloat(m VariantMap, key String) float64 {
    var val = C.QtVariantMapGetFloat(C.QtVariantMap(m), C.QtString(key))
    return float64(val)
}
func VariantMapGetBool(m VariantMap, key String) bool {
    var val = C.QtVariantMapGetBool(C.QtVariantMap(m), C.QtString(key))
    return getBool(val)
}

type ImageData struct {
    Bytes   [] byte
    Format  ImageDataFormat
}
type ImageDataFormat int
const (
    PNG  ImageDataFormat  =  iota
    JPEG
)
type iconPool (map[*ImageData] iconPoolItem)
type iconPoolItem struct {
    icon  Icon
    del   func()
}
func makeIconPool() (iconPool, func()) {
    var p = make(iconPool)
    return p, func() {
        for _, item := range p {
            item.del()
        }
    }
}
func (p iconPool) GetIcon(img *ImageData) Icon {
    var cached, is_cached = p[img]
    if is_cached {
        return cached.icon
    } else {
        var new_icon, del_icon = (func() (Icon, func()) {
            if img != nil {
                var pm, del_pm = NewPixmap(img.Bytes, img.Format)
                defer del_pm()
                return NewIcon(pm)
            } else {
                return NewIconEmpty()
            }
        })()
        p[img] = iconPoolItem {
            icon: new_icon,
            del:  del_icon,
        }
        return new_icon
    }
}
func NewPixmap(data ([] byte), format ImageDataFormat) (Pixmap, func()) {
    var buf, length = addrlen(data)
    if format == PNG {
        var pm = C.QtNewPixmapPNG(buf, length)
        return Pixmap(pm), func() { C.QtDeletePixmap(pm) }
    } else if format == JPEG {
        var pm = C.QtNewPixmapJPEG(buf, length)
        return Pixmap(pm), func() { C.QtDeletePixmap(pm) }
    } else {
        panic("qt pixmap: unsupported image format")
    }
}
func NewIcon(pm Pixmap) (Icon, func()) {
    var icon = C.QtNewIcon(C.QtPixmap(pm))
    return Icon(icon), func() {
        C.QtDeleteIcon(icon)
    }
}
func NewIconEmpty() (Icon, func()) {
    var icon = C.QtNewIconEmpty()
    return Icon(icon), func() {
        C.QtDeleteIcon(icon)
    }
}

// NOTE: should be called on main thread
func Init() {
    C.QtInit()
}
// NOTE: should be called on main thread
func Main() {
    C.QtMain()
}
// NOTE: should be called on main thread
func Exit(code int32) {
    C.QtExit(C.int(code))
}

// Generate a UUID
func UUID() string {
    var s = ConsumeString(String(C.QtNewUUID()))
    s = strings.TrimPrefix(s, "{")
    s = strings.TrimSuffix(s, "}")
    return s
}
// Invokes the `operation` callback in the Qt main thread.
func CommitTask(operation func()) {
    var delete_callback (func() bool)
    var f = func() {
        operation()
        delete_callback()
    }
    callback, delete_callback := cgohelper.NewCallback(f)
    C.QtCommitTask(cgo_callback, C.size_t(callback))
}
// Loads `ui_xml` with `base_dir` and execute `k` in the Qt main thread.
func LoadWidget(ui_xml string, base_dir string, k func(Widget)(error)) error {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var channel = make(chan error)
    CommitTask(func() {
        var ptr = C.QtLoadWidget(new_str(ui_xml), new_str(base_dir))
        if ptr != nil {
            channel <- k(Widget{Object{ptr}})
        } else {
            channel <- errors.New("failed to load widget from ui xml")
        }
    })
    return <- channel
}
// Makes a signal connection. `callback` will be called in the Qt main thread.
func Connect(obj Object, signal string, callback func()) func() {
    var new_str, del_all_str = str_alloc()
    defer del_all_str()
    var cb, del_cb = cgohelper.NewCallback(callback)
    var channel = make(chan C.QtConnHandle)
    // Note: Although connect() is documented as "thread-safe",
    //       it is not clear what will happen if the goroutine
    //       is preempted and moved to another thread while
    //       calling connect(), thus CommitTask() is used here.
    CommitTask(func() {
        var conn = C.QtConnect(obj.ptr, new_str(signal), cgo_callback, C.size_t(cb))
        channel <- conn
    })
    var conn = <- channel
    if getBool(C.QtIsConnectionValid(conn)) {
        return func() {
            var wait = make(chan struct{})
            CommitTask(func() {
                C.QtDisconnect(conn)
                // Note: Use CommitTask() to prevent pending callbacks
                //       from being removed.
                CommitTask(func() {
                    del_cb()
                })
                wait <- struct{}{}
            })
            <- wait
        }
    } else {
        panic("invalid connection")
    }
}
// Installs an event filter. `callback` will be called in the Qt main thread.
func Listen(obj Object, kind EventKind, prevent bool, callback func(Event)) func() {
    var l C.QtEventListener
    var cb, del_cb = cgohelper.NewCallback(func() {
        var ev = C.QtGetCurrentEvent(l)
        callback(Event(ev))
    })
    var ok = make(chan struct{})
    CommitTask(func() {
        var prevent_flag = makeQtBool(prevent)
        l = C.QtAddEventListener(obj.ptr, C.size_t(kind), prevent_flag, cgo_callback, C.size_t(cb))
        ok <- struct{} {}
    })
    <- ok
    return func() {
        var wait = make(chan struct{})
        CommitTask(func() {
            C.QtRemoveEventListener(obj.ptr, l)
            // Note: Use CommitTask() to prevent pending callbacks
            //       from being removed.
            CommitTask(func() {
                del_cb()
            })
            wait <- struct{}{}
        })
        <- wait
    }
}

func (a Action) OnTrigger(cb func()) func() {
    return Connect(a.Object, "triggered()", cb)
}

func (w MainWindow) OnClose(cb func()) func() {
    return Listen(w.Object, EventClose(), true, func(_ Event) {
        cb()
    })
}

func (w Dialog) OnClose(cb func()) func() {
    return Listen(w.Object, EventClose(), true, func(_ Event) {
        cb()
    })
}
func (w Dialog) OnRejected(cb func()) func() {
    return Connect(w.Object, "rejected()", cb)
}
func (w Dialog) Exec() {
    C.QtDialogExec(w.ptr)
}
func (w Dialog) Accept() {
    C.QtDialogAccept(w.ptr)
}
func (w Dialog) Reject() {
    C.QtDialogReject(w.ptr)
}
func (w Dialog) ShowModal() {
    C.QtDialogShowModal(w.ptr)
}

func (w Label) SetText(content string) {
    w.SetPropString("text", content)
}

func (w PushButton) OnClick(cb func()) func() {
    return Connect(w.Object, "clicked()", cb)
}

func (w LineEdit) SetText(content string) {
    _, unblock := w.BlockSignals()
    defer unblock()
    w.SetPropString("text", content)
}
func (w LineEdit) SetTextWithSignal(content string) {
    w.SetPropString("text", content)
}
func (w LineEdit) Text() string {
    return w.GetPropString("text")
}
func (w LineEdit) WatchText(cb func(string)) func() {
    return Connect(w.Object, "textChanged(const QString&)", func() {
        cb(w.Text())
    })
}

func (w PlainTextEdit) SetText(content string) {
    _, unblock := w.BlockSignals()
    defer unblock()
    w.SetPropString("plainText", content)
}
func (w PlainTextEdit) Text() string {
    return w.GetPropString("plainText")
}
func (w PlainTextEdit) WatchText(cb func(string)) func() {
    return Connect(w.Object, "textChanged()", func() {
        cb(w.Text())
    })
}

type ListWidgetItem struct {
    Key    string
    Icon   *ImageData
    Label  string
}
func (w ListWidget) Clear() {
    C.QtListWidgetClear(w.ptr)
}
func (w ListWidget) CurrentItemKey() (string, bool) {
    if getBool(C.QtListWidgetHasCurrentItem(w.ptr)) {
        var raw_key = C.QtListWidgetGetCurrentItemKey(w.ptr)
        var key = ConsumeString(String(raw_key))
        return key, true
    } else {
        return "", false
    }
}
func (w ListWidget) WatchCurrentItemKey(cb func(string,bool)) func() {
    return Connect(w.Object, "currentItemChanged(QListWidgetItem*,QListWidgetItem*)", func() {
        cb(w.CurrentItemKey())
    })
}
func (w ListWidget) SetItems(items ([] ListWidgetItem), current *string) {
    var occurred_keys = make(map[string] bool)
    var current_key string
    var has_current = false
    for _, item := range items {
        if occurred_keys[item.Key] {
            panic(fmt.Sprintf("qt.ListWidget: duplicate item key %s", item.Key))
        }
        occurred_keys[item.Key] = true
        var is_current = (current != nil && item.Key == *current)
        if is_current {
            has_current = true
            current_key = item.Key
        }
    }
    var prev_current_key, prev_has_current = w.CurrentItemKey()
    (func() {
        if has_current {
            _, unblock := w.BlockSignals()
            defer unblock()
        }
        w.Clear()
    })()
    if has_current && prev_has_current {
        if current_key == prev_current_key {
            _, unblock := w.BlockSignals()
            defer unblock()
        }
    }
    var icon_pool, dispose = makeIconPool()
    defer dispose()
    for _, item := range items {
        var key, del_key = NewString(item.Key)
        var icon = icon_pool.GetIcon(item.Icon)
        var label, del_label = NewString(item.Label)
        var is_current = (has_current && (item.Key == current_key))
        C.QtListWidgetAddItem(
            w.ptr, C.QtString(key),
            C.QtIcon(icon), C.QtString(label),
            makeQtBool(is_current),
        )
        del_label()
        del_key()
    }
}

type TreeWidgetItem struct {
    Key       string
    Icon      *ImageData
    Labels    [] string
    Children  [] TreeWidgetItem
}
func (w TreeWidget) Clear() {
    C.QtTreeWidgetClear(w.ptr)
}
func (w TreeWidget) CurrentItemKey() (string, bool) {
    if getBool(C.QtTreeWidgetHasCurrentItem(w.ptr)) {
        var raw_key = C.QtTreeWidgetGetCurrentItemKey(w.ptr)
        var key = ConsumeString(String(raw_key))
        return key, true
    } else {
        return "", false
    }
}
func (w TreeWidget) WatchCurrentItemKey(cb func(string,bool)) func() {
    return Connect(w.Object, "currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)", func() {
        cb(w.CurrentItemKey())
    })
}
func (w TreeWidget) SetItems(items ([] TreeWidgetItem), current *string) {
    var occurred_keys = make(map[string] bool)
    var current_key string
    var has_current = false
    var check_keys func(TreeWidgetItem)
    check_keys = func(item TreeWidgetItem) {
        if occurred_keys[item.Key] {
            panic(fmt.Sprintf("qt.TreeWidget: duplicate item key %s", item.Key))
        }
        occurred_keys[item.Key] = true
        var is_current = (current != nil && item.Key == *current)
        if is_current {
            has_current = true
            current_key = item.Key
        }
        for _, child := range item.Children {
            check_keys(child)
        }
    }
    for _, item := range items {
        check_keys(item)
    }
    var prev_current_key, prev_has_current = w.CurrentItemKey()
    (func() {
        if has_current {
            _, unblock := w.BlockSignals()
            defer unblock()
        }
        w.Clear()
    })()
    if has_current && prev_has_current {
        if current_key == prev_current_key {
            _, unblock := w.BlockSignals()
            defer unblock()
        }
    }
    var column_count = int(C.QtTreeWidgetGetColumnCount(w.ptr))
    var icon_pool, dispose = makeIconPool()
    defer dispose()
    var add_item func(TreeWidgetItem, unsafe.Pointer)
    add_item = func(item TreeWidgetItem, parent unsafe.Pointer) {
        var key, del_key = NewString(item.Key)
        defer del_key()
        var icon = icon_pool.GetIcon(item.Icon)
        var labels = make([] String, column_count)
        var labels_del = make([] func(), column_count)
        defer (func() { for _, del := range labels_del { del() } })()
        if len(item.Labels) > column_count {
            panic("qt.TreeWidget: too many labels")
        }
        for i, label := range item.Labels {
            var label_q, label_del = NewString(label)
            labels[i] = label_q
            labels_del[i] = label_del
        }
        var is_current = (has_current && (item.Key == current_key))
        var labels_ptr unsafe.Pointer
        if len(labels) > 0 {
            labels_ptr = unsafe.Pointer(&labels[0])
        }
        var item_ptr = C.QtTreeWidgetAddItem(
            w.ptr, parent, C.QtString(key),
            C.QtIcon(icon), (*C.QtString)(labels_ptr),
            makeQtBool(is_current),
        )
        for _, child := range item.Children {
            add_item(child, item_ptr)
        }
    }
    for _, item := range items {
        add_item(item, nil)
    }
}

func (w TextBrowser) SetHtml(content string) {
    w.Object.SetPropString("html", content)
}

func (w WebEngineView) DisableContextMenu() {
    C.QtWebViewDisableContextMenu(w.ptr)
}
func (w WebEngineView) EnableLinkDelegation() {
    C.QtWebViewEnableLinkDelegation(w.ptr)
}
func (w WebEngineView) SetHTML(html String, base_url String) {
    C.QtWebViewSetHTML(w.ptr, C.QtString(html), C.QtString(base_url))
}
func (w WebEngineView) ScrollToAnchor(anchor String) {
    C.QtWebViewScrollToAnchor(w.ptr, C.QtString(anchor))
}
func (w WebEngineView) GetScroll() Point {
    return getPoint(C.QtWebViewGetScroll(w.ptr))
}
func (w WebEngineView) SetScroll(pos Point) {
    C.QtWebViewSetScroll(w.ptr, makeQtPoint(pos))
}

func WebGetAssetURL(key String) String {
    return String(C.WebGetAssetURL(C.QtString(key)))
}
func (view CustomWebView) LoadContent() {
    C.WebViewLoadContent(view.ptr)
}
func (view CustomWebView) RegisterAsset(key String, mime String, data ([] byte))  {
    var buf, length = addrlen(data)
    C.WebViewRegisterAsset(view.ptr, C.QtString(key), C.QtString(mime), buf, length)
}
func (view CustomWebView) InjectCSS(key String) {
    C.WebViewInjectCSS(view.ptr, C.QtString(key))
}
func (view CustomWebView) InjectJS(key String) {
    C.WebViewInjectJS(view.ptr, C.QtString(key))
}
func (view CustomWebView) InjectTTF(key String, family String, weight String, style String) {
    C.WebViewInjectTTF(view.ptr, C.QtString(key), C.QtString(family), C.QtString(weight), C.QtString(style))
}
func (view CustomWebView) PatchActualDOM(patch_data ([] byte)) {
    var str, del = NewStringFromUtf8Binary(patch_data)
    defer del()
    C.WebViewPatchActualDOM(view.ptr, C.QtString(str))
}
func (view CustomWebView) GetCurrentEventHandler() string {
    var raw_id = C.WebViewGetCurrentEventHandler(view.ptr)
    var id_str = ConsumeString(String(raw_id))
    return id_str
}
func (view CustomWebView) GetCurrentEventPayload() *WebEventPayload {
    return &WebEventPayload {
        VariantMap(C.WebViewGetCurrentEventPayload(view.ptr)),
    }
}

type WebEventPayload struct {
    Data  VariantMap
}
func (ev *WebEventPayload) Consume(f func(*WebEventPayload) interface{}) interface{} {
    defer func() {
        C.QtDeleteVariantMap(C.QtVariantMap(ev.Data))
    } ()
    return f(ev)
}
func (ev *WebEventPayload) GetString(key string) string {
    var key_str, del = NewString(key)
    defer del()
    return VariantMapGetString(ev.Data, key_str)
}
func (ev *WebEventPayload) GetFloat(key string) float64 {
    var key_str, del = NewString(key)
    defer del()
    return VariantMapGetFloat(ev.Data, key_str)
}
func (ev *WebEventPayload) GetBool(key string) bool {
    var key_str, del = NewString(key)
    defer del()
    return VariantMapGetBool(ev.Data, key_str)
}

type WebWindowOptions struct {
    Title     string
    Kind      WindowKind
    Icon      *ImageData
    Width     uint
    Height    uint
    CloseBtn  bool
    Modal     bool
    Debug     bool
}
type WindowKind C.size_t
func WindowKindOrdinary() WindowKind { return WindowKind(C.QtWindowKindOrdinary) }
func WindowKindDialog() WindowKind { return WindowKind(C.QtWindowKindDialog) }
func WindowKindPopup() WindowKind { return WindowKind(C.QtWindowKindPopup) }
//go:embed default-window-icon.png
var defaultWindowIconData ([] byte)
func WebWindowCreate(parent Widget, opts WebWindowOptions) (WebWindow, CustomWebView, func()) {
    var title_str, del_title = NewString(opts.Title)
    defer del_title()
    var icon_data *ImageData
    if opts.Icon != nil {
        icon_data = opts.Icon
    } else {
        icon_data = &ImageData {
            Bytes:  defaultWindowIconData,
            Format: PNG,
        }
    }
    var icon_pm, del_icon_pm = NewPixmap(icon_data.Bytes, icon_data.Format)
    defer del_icon_pm()
    var icon_obj, del_icon_obj = NewIcon(icon_pm)
    defer del_icon_obj()
    var w_ptr = C.WebWindowCreate (
        parent.ptr,
        C.size_t(opts.Kind),
        C.QtIcon(icon_obj),
        C.QtString(title_str),
        C.int(opts.Width),
        C.int(opts.Height),
        makeQtBool(opts.CloseBtn),
        makeQtBool(opts.Modal),
        makeQtBool(opts.Debug),
    )
    var view_ptr = C.WebWindowGetWebView(w_ptr)
    { var dialog = WebWindow{MainWindow{Widget{Object{w_ptr}}}}
    var view = CustomWebView{WebEngineView{Widget{Object{view_ptr}}}}
    var dispose = func() {
        var wait = make(chan struct{})
        CommitTask(func() {
            // behavior: hide and deleteLater
            C.WebWindowDispose(w_ptr)
            wait <- struct{}{}
        })
        <- wait
    }
    return dialog, view, dispose }
}
func (w WebWindow) Show(set_pos bool, x int32, y int32) {
    C.WebWindowShow(w.ptr, makeQtBool(set_pos), C.int(x), C.int(y))
}

func ColorDialog(parent Widget, title_ string, value Color) (Color, bool) {
    var title, del = NewString(title_)
    defer del()
    var ok = getBool(C.QtColorDialog(
        parent.ptr,
        C.QtString(title),
        (*C.size_t)(unsafe.Pointer(&(value.H))),
        (*C.size_t)(unsafe.Pointer(&(value.S))),
        (*C.size_t)(unsafe.Pointer(&(value.L))),
        (*C.size_t)(unsafe.Pointer(&(value.A))),
    ))
    if ok {
        return value, true
    } else {
        return Color {}, false
    }
}

type FileDialogOptions struct {
    Title   string
    Cwd     string
    Filter  string
}
func fileDialogAdaptOptions(opts FileDialogOptions) (String, String, String, func()) {
    var title, del_title = NewString(opts.Title)
    var cwd, del_cwd = NewString(opts.Cwd)
    var filter, del_filter = NewString(opts.Filter)
    return title, cwd, filter, func() {
        del_title()
        del_cwd()
        del_filter()
    }
}
func FileDialogOpen(parent Widget, opts FileDialogOptions) string {
    var title, cwd, filter, del = fileDialogAdaptOptions(opts)
    defer del()
    var raw_path = C.QtFileDialogOpen(
        parent.ptr,
        C.QtString(title), C.QtString(cwd), C.QtString(filter),
    )
    return ConsumeString(String(raw_path))
}
func FileDialogOpenMultiple(parent Widget, opts FileDialogOptions) ([] string) {
    var title, cwd, filter, del = fileDialogAdaptOptions(opts)
    defer del()
    var raw_path_list = C.QtFileDialogOpenMultiple(
        parent.ptr,
        C.QtString(title), C.QtString(cwd), C.QtString(filter),
    )
    var path_list = make([] string, 0)
    var L = uint(C.QtStringListGetSize(raw_path_list))
    for i := uint(0); i < L; i += 1 {
        var raw_item = C.QtStringListGetItem(raw_path_list, C.size_t(i))
        var item = ConsumeString(String(raw_item))
        path_list = append(path_list, item)
    }
    C.QtDeleteStringList(raw_path_list)
    return path_list
}
func FileDialogSelectDirectory(parent Widget, opts FileDialogOptions) string {
    var title, cwd, _, del = fileDialogAdaptOptions(opts)
    defer del()
    var raw_path = C.QtFileDialogSelectDirectory(
        parent.ptr,
        C.QtString(title), C.QtString(cwd),
    )
    return ConsumeString(String(raw_path))
}
func FileDialogSave(parent Widget, opts FileDialogOptions) string {
    var title, cwd, filter, del = fileDialogAdaptOptions(opts)
    defer del()
    var raw_path = C.QtFileDialogSave(
        parent.ptr,
        C.QtString(title), C.QtString(cwd), C.QtString(filter),
    )
    return ConsumeString(String(raw_path))
}

func InputDialog(parent Widget, title string, prompt string, password bool, text *string) bool {
    var title_q, del_title = NewString(title)
    var prompt_q, del_content = NewString(prompt)
    var text_q, del_text = NewString(*text)
    defer del_title()
    defer del_content()
    defer del_text()
    var ok = getBool(C.QtInputDialog(
        parent.ptr,
        C.QtString(title_q),
        C.QtString(prompt_q),
        makeQtBool(password),
        C.QtString(text_q),
    ))
    if ok {
        *text = copyString(text_q)
    }
    return ok
}

func InputDialogInt32(parent Widget, title string, prompt string, min int32, max int32, step int32, value *int32) bool {
    var title_q, del_title = NewString(title)
    var prompt_q, del_content = NewString(prompt)
    var value_q C.int
    defer del_title()
    defer del_content()
    var ok = getBool(C.QtInputDialogInt(
        parent.ptr,
        C.QtString(title_q),
        C.QtString(prompt_q),
        C.int(min),
        C.int(max),
        C.int(step),
        &value_q,
    ))
    if ok {
        *value = int32(value_q)
    }
    return ok
}

func InputDialogFloat64(parent Widget, title string, prompt string, min float64, max float64, step float64, decimals int32, value *float64) bool {
    var title_q, del_title = NewString(title)
    var prompt_q, del_content = NewString(prompt)
    var value_q C.double
    defer del_title()
    defer del_content()
    var ok = getBool(C.QtInputDialogDouble(
        parent.ptr,
        C.QtString(title_q),
        C.QtString(prompt_q),
        C.double(min),
        C.double(max),
        C.double(step),
        C.int(decimals),
        &value_q,
    ))
    if ok {
        *value = float64(value_q)
    }
    return ok
}

func MessageBoxConfirm(parent Widget, title string, content string) bool {
    var title_q, del_title = NewString(title)
    var content_q, del_content = NewString(content)
    defer del_title()
    defer del_content()
    return getBool(C.QtMessageBoxConfirm(
        parent.ptr,
        C.QtString(title_q), C.QtString(content_q),
    ))
}
func MessageBoxError(parent Widget, title string, content string) {
    var title_q, del_title = NewString(title)
    var content_q, del_content = NewString(content)
    defer del_title()
    defer del_content()
    C.QtMessageBoxError(
        parent.ptr,
        C.QtString(title_q), C.QtString(content_q),
    )
}

type Kvdb struct {
    ptr  unsafe.Pointer
}
func KvdbOpen(path string, exclusive bool) (Kvdb, error) {
    var path_q, del_path = NewString(path)
    defer del_path()
    var d Kvdb
    var ok = getBool(
        C.KvdbOpen(&(d.ptr), C.QtString(path_q), makeQtBool(exclusive)),
    )
    if ok {
        return d, nil
    } else {
        return Kvdb {}, fmt.Errorf("kvdb: failed to open database: %s", path)
    }
}
func (d Kvdb) Close() {
    C.KvdbClose(d.ptr)
}
func (d Kvdb) Insert(key string, category int32, value ([] byte)) error {
    var key_q, del_key = NewString(key)
    defer del_key()
    var vb, vl = addrlen(value)
    var ok = getBool(
        C.KvdbInsert(d.ptr, C.QtString(key_q), C.int(category), vb, vl),
    )
    if ok {
        return nil
    } else {
        return errors.New("kvdb: failed to insert record")
    }
}
func (d Kvdb) Remove(key string) error {
    var key_q, del_key = NewString(key)
    defer del_key()
    var ok = getBool(
        C.KvdbRemove(d.ptr, C.QtString(key_q)),
    )
    if ok {
        return nil
    } else {
        return errors.New("kvdb: failed to remove record")
    }
}
func (d Kvdb) Update(key string, value ([] byte)) error {
    var key_q, del_key = NewString(key)
    defer del_key()
    var vb, vl = addrlen(value)
    var ok = getBool(
        C.KvdbUpdate(d.ptr, C.QtString(key_q), vb, vl),
    )
    if ok {
        return nil
    } else {
        return errors.New("kvdb: failed to update record")
    }
}
func (d Kvdb) Lookup(key string) ([] byte, bool, error) {
    var key_q, del_key = NewString(key)
    defer del_key()
    var value ByteArray
    var found C.QtBool
    var ok = getBool(
        C.KvdbLookup(d.ptr, C.QtString(key_q),
            &found, (*C.QtByteArray)(&value)),
    )
    if ok {
        if getBool(found) {
            return ConsumeByteArray(value), true, nil
        } else {
            return nil, false, nil
        }
    } else {
        return nil, false, errors.New("kvdb: failed to lookup record")
    }
}
func (d Kvdb) ReadAll() (KvdbSelectQuery, error) {
    var q KvdbSelectQuery
    var ok = getBool(C.KvdbReadAll(d.ptr, (*C.KvdbSelectQuery)(&q)))
    if ok {
        return q, nil
    } else {
        return KvdbSelectQuery {}, errors.New("kvdb: failed to read records")
    }
}
func (d Kvdb) ReadCategory(category int) (KvdbSelectQuery, error) {
    var q KvdbSelectQuery
    var ok = getBool(
        C.KvdbReadCategory(d.ptr, C.int(category), (*C.KvdbSelectQuery)(&q)),
    )
    if ok {
        return q, nil
    } else {
        return KvdbSelectQuery {}, errors.New("kvdb: failed to read records")
    }
}
type KvdbSelectQuery C.KvdbSelectQuery
func (q KvdbSelectQuery) Consume(item func(string, ([] byte))) {
    var key String
    var value ByteArray
    for getBool(C.KvdbSelectQueryNext(
        C.KvdbSelectQuery(q),
        (*C.QtString)(&key),
        (*C.QtByteArray)(&value),
    )) {
        item(ConsumeString(key), ConsumeByteArray(value))
    }
    C.KvdbDeleteSelectQuery(C.KvdbSelectQuery(q))
}


